package ru.chaykin.rendering;

import static ru.chaykin.load.parse.MIFParser.getRoot;
import static ru.chaykin.rendering.CullingTextThread.TEX_BUFF_SIZE;
import static ru.chaykin.rendering.CullingThread.ra;

import java.nio.FloatBuffer;
import java.util.List;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import ru.chaykin.load.parse.MIFParser;
import ru.chaykin.measures.IMeasure;
import ru.chaykin.measures.Mark;
import ru.chaykin.primitives.Point;
import ru.chaykin.quadtree.QuadTree;
import ru.chaykin.util.Region;
import ru.chaykin.viewobject.ViewObject;
import ru.chaykin.viewobject.geometry.Geometry;
import ru.chaykin.viewobject.texture.TextureFactory;
import android.opengl.GLSurfaceView;
import android.os.SystemClock;
import android.util.Log;

public class OpenGLSceneRenderer implements GLSurfaceView.Renderer {

	//TODO В настройках предусмотреть включение/выключение прилипания и расстояние, с которого начинаем прилипать
	FloatBuffer mark;
	
	private static final float bottom = -1.0f;	//Нижняя плоскость отсечения
	private static final float top = 1.0f;		//Верхняя плоскость отсечения 
	private static final float near = 1.0f; 	//Ближняя плоскость отсечения в функции glFrustumf

	public static final int OBJECT_NOT_SELECTED = -1;
	private static final short ADJASTING_RADIUS = 1; //TODO Вынести в настройки
	
	private static float far = 10.0f;		//Дальняя плоскость отсечения
	
	private static int w;
	private static int h;
	
	private float dx = 0;
	private float dy = 0;
	private static float posX = 0;
	private static float posY = 0;
	private volatile static float translateX = 0;
	private volatile static float translateY = 0;
	private static float translateZ = -1.2f;
	private static GL10 gl;
	
	private IMeasure measure;

	private static float ratio;
	
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		OpenGLSceneRenderer.gl = gl;
		QuadTree.setGL(gl);
		gl.glLoadIdentity();
		gl.glEnable (GL10.GL_DEPTH_TEST);
		TextureFactory.genTexIds(TEX_BUFF_SIZE, gl);
	}
	
	public void onSurfaceChanged(GL10 gl, int w, int h) {
		OpenGLSceneRenderer.w = w;
		OpenGLSceneRenderer.h = h;
		ratio = (float) w / h;
		gl.glViewport(0, 0, w, h);
	    gl.glMatrixMode(GL10.GL_PROJECTION);
	    gl.glLoadIdentity();
	    gl.glFrustumf(-ratio, ratio, bottom, top, near, far);
	   
	    // Обнуляем буфер текстур. Если не обнулять, как-то странно все работать начинает 
	    for (int i = 0; i < TEX_BUFF_SIZE; i++) {
			synchronized(CullingTextThread.textureBuff) {
				CullingTextThread.textureBuff[i] = null;
			}
	    }
	}
	
	public static void initRenderedArea() {
		// Применяем начальное положение камеры
	    synchronized (ra) { 
	    	posX = -CullingThread.getRenderedArea().get_x();
	    	posY = -CullingThread.getRenderedArea().get_y();
	    	translateZ = -CullingThread.getRenderedArea().get_r();
	    	far = -translateZ + 0.01f;
	    }
	    if (gl != null) {
	    	gl.glFrustumf(-ratio, ratio, bottom, top, near, far);
	    }
	}
	
	public float getGL_X(float x) {
		return -(2 * ratio * x / w - ratio) * translateZ / near - translateX;
	}

	public float getGL_Y(float y) {
		return (2 * y / h - 1) * translateZ / near - translateY;
	}
	
	/** 
	 * Метод всегда возвращает 0, т. к. все объекты рисуются в плоскости z = 0
	 */
	public float getGL_Z(int notUsed) {
		return 0;
	}
	
	/**
	 * Устанавливает марку в выбранную пользователем точку. Точка выравнивается до вершины примитива, если она ближе критического значения
	 * @param x, y - экраные координаты (в пикселях)
	 * @return фактически установленные OpenGL-координаты выбранной точки
	 */
	public float[] setPoint(float x, float y) {
		float[] adjPnt = getRoot().adjastPoint(getGL_X(x), getGL_Y(y), ra);
		mark = Mark.getMark(adjPnt, 0, -translateZ / 24.0f);
		return adjPnt;
	}
	
	public void clearMark() {
		mark = null;
	}
	
	private	static long	time;
	private	static short framerate;
	private	static long	fpsTime;
	private	static long	frameTime;
	private	static float avgFPS;
	
	private final void calcFPS() {
		time = SystemClock.uptimeMillis();

		if (time >= (frameTime + 1000.0f)) {
			frameTime = time;
			avgFPS += framerate;
			framerate = 0;
		}
			
		if (time >= (fpsTime + 3000.0f)) {
			fpsTime = time;
			avgFPS /= 3.0f;
			Log.d(getClass().getName(), "FPS: " + Float.toString(avgFPS));
			avgFPS = 0;
		}
		framerate++;
	}
	// Сглаживание - хорошая штука, но как-то сложно реализуется на Android + ресурсы телефона и без того на пределе...
	public void onDrawFrame(GL10 gl) {
//		calcFPS();
		gl.glClearColor(1f, 1f, 1f, 1f);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();
		translateX = posX + dx;
		translateY = posY + dy;
		gl.glTranslatef(translateX, translateY, translateZ);
		
		gl.glDisable(GL10.GL_DEPTH_TEST);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		gl.glEnable(GL10.GL_BLEND);
		
		for (int i = 0; i < TEX_BUFF_SIZE; i++) {
			synchronized(CullingTextThread.textureBuff) {
				if (CullingTextThread.textureBuff[i] != null) 
				{
					TextureFactory.bindTextureIfNeeded(gl, i);
					CullingTextThread.textureBuff[i].draw(gl);
				}
			}
		}
		gl.glDisable(GL10.GL_BLEND);
		gl.glEnable(GL10.GL_DEPTH_TEST);
		
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		if (getRoot() != null) {
			synchronized(MIFParser.getRoot()) {
				getRoot().drawObjects(ra);
			}
		}
		if (mark != null)
			drawMark(gl);
		if (measure != null)
			measure.draw(gl);
		
	    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
	    gl.glDisable(GL10.GL_DEPTH_TEST);
	}
	
	public int getSelectedObjectId(float x, float y) {
		x = getGL_X(x);
		y = getGL_Y(y);
		float minRad = Float.MAX_VALUE;
		float minRegionRad = Float.MAX_VALUE;
		float dst;
		int id = OBJECT_NOT_SELECTED;
		int regionId = OBJECT_NOT_SELECTED;
		FloatBuffer buff;
		boolean isRegion = false;
		List<ViewObject> objList = getRoot().getRenderedObjects(ra);
		for (int j = 0; j < objList.size(); j++) {
			if (objList.get(j).getBoundCircle().in(CullingThread.ra)) {
				buff = ((Geometry) objList.get(j)).getBuff();
				isRegion = objList.get(j).isRegion();
				objList.get(j).setSelected(false);
			}
			else
				buff = null;
			if (buff != null) {
				if (isRegion && Region.pointInRegion(new Point(x, y), buff)) {
					for (int i = 0; i < buff.position(); i += 3) {
						dst = Point.distance(x, y, buff.get(i), buff.get(i + 1));
						if (dst < minRegionRad) {
							minRegionRad = dst;
							regionId = j;
						}
					}
				}
				
				if (!isRegion)
				for (int i = 0; i < buff.position(); i += 3) {
					dst = Point.distance(x, y, buff.get(i), buff.get(i + 1));
					if (dst < minRad) {
						minRad = dst;
						id = j;
					}
				}
			}
		}
		if (id != OBJECT_NOT_SELECTED && minRad < ADJASTING_RADIUS) {
			objList.get(id).setSelected(true);
			return objList.get(id).getMIDIndex();
		}
		if (regionId != OBJECT_NOT_SELECTED) {
			objList.get(regionId).setSelected(true);
			return objList.get(regionId).getMIDIndex();
		}
		return OBJECT_NOT_SELECTED;
	}
	
	private final void drawMark(GL10 gl) {
		gl.glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
	    gl.glLineWidth(2);
	    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mark);
		gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, 9);
		gl.glColor4f(0.0f, 0.5f, 0.0f, 0.9f);
		gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 9);
	}

	public void addPointToMeasure(float x, float y) {
		measure.add(new Point(getRoot().adjastPoint(getGL_X(x), getGL_Y(y), ra)));
	}
	
	public void setMeasure(IMeasure measure) {
		this.measure = measure;
	}
	
	public IMeasure getMeasure() {
		return measure;
	}
	
	public void move(float dx, float dy) {
		this.dx = -2 * ratio * dx / w  * translateZ / near;
		this.dy =  2 * dy / h  * translateZ / near;
		CullingThread.updateRenderedArea(-translateX, -translateY);
	}
	
	public void savePos(float dx, float dy) {
		posX += -2 * ratio * dx / w  * translateZ / near;
		posY +=  2 * dy / h  * translateZ / near;
		this.dx=0;
		this.dy=0;
	}

	public void zoom(float deltaZoom) {
		CullingThread.updateRenderedArea(Point.distance(getGL_X(0), getGL_Y(0), getGL_X(w / 2f), getGL_Y(h / 2f)));
		translateZ += -2 * deltaZoom / h * translateZ / near;
		if (translateZ >= -near)
			translateZ = -near - 0.01f;
		if (translateZ <= -far)
			translateZ = -far + 0.01f;
		if (measure != null)
			measure.zoom(translateZ);
		mark =null;	//Стираем марку
	}
}
